# MessageBusProxy - can be used to connect cliet processes to backend MessageBus
# Copyright (C) 2007 Lauri Taimila
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = "GPLv2"
__copyright__ = "2007, Lauri Taimila"
__author__ = "Lauri Taimila <lauri@taimila.com>"

import os
import socket
import cPickle
import threading
import ConfigParser
from cStringIO import StringIO

from backend.core.message import Message
from backend.core.message_type_priority import MessageType

class MessageBusProxy(threading.Thread):
    """
    MessageBusProxy connects client processes to the backend's MessageBus.
    
    MessageBusProxy hides IPC (inter process communication) from clients and
    it provides easy way to send and receive Messages to/from backends
    MessageBus.
    
    Under the hood MessageBusProxy transfers Message objects via socket
    connection. Connection is made to backend's ConnectionServer object which
    spawns a new thread for each client connection.
    
    If your client doesn't want to receive any messages - only send them,
    them do NOT give MessageHandler as a constructor parameter and do NOT
    give message_type_dictionary as a paramter and do NOT call 
    proxy_object.start(). If you're client is interested in messages,
    set MessageHandler, dictionary and call start() right after 
    connectToMessageBus() call.
    """

    def __init__(self, message_type_dictionary = {}, 
                       message_handler = None, 
                       client_name = "Unknown client"):
        """
        Create a new MessageBusProxy object
        @param message_type_dictionary: Dictionary that contains message types
        @param message_handler: MessageHandler object
        @param client_name: Name of the client (as string)
        """
        threading.Thread.__init__(self)
        self.message_type_dictionary = message_type_dictionary
        self.message_handler = message_handler
        self.client_name = client_name
        self.socket_to_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.socket_as_file = self.socket_to_server.makefile()
    
    def getPort(self):
        """
        Get backend ConnectionServer's port number.
        @return: Port number as integer
        """
        #FIXME: We should use Configuration object instead
        preferences_cfg = os.path.expanduser('~/.entertainer/preferences.conf')
        try:
            preferences = ConfigParser.ConfigParser()
            preferences.readfp(open(preferences_cfg))
            return preferences.getint("General", "backend_port")
        except:
            return 45054 # Default            
        
    def connectToMessageBus(self):
        """
        Connects to backend's MessageBus.
        
        After this call, all interesting messages are delivered to local
        MessageHandler.
        """
        # Open socket
        self.socket_to_server.connect(('localhost', self.getPort()))

        # Send client name
        self.socket_to_server.sendall(self.client_name + "\n")
        
        # Send message type/priority dictionary
        message_types = cPickle.dumps(self.message_type_dictionary,
                                      cPickle.HIGHEST_PROTOCOL)
        self.socket_to_server.sendall(message_types + "\n")
        
    def disconnectFromMessageBus(self):
        """Disconnect from backend's MessageBus"""
        self.socket_to_server.shutdown(socket.SHUT_RDWR)
        self.socket_to_server.close()
        
    def sendMessage(self, message):
        """Send messaged to backend's MessageBus"""
        message_str = cPickle.dumps(message, cPickle.HIGHEST_PROTOCOL)
        self.socket_to_server.sendall(message_str)
        self.socket_to_server.sendall("\nEND_OF_MESSAGE_OBJECT\n")
    
    def run(self):
        obj_buffer = StringIO()
        while True:
            line = self.socket_as_file.readline()
            if line == "END_OF_MESSAGE_OBJECT\n":
                message = cPickle.loads(obj_buffer.getvalue())
                if self.message_handler is not None:
                    self.message_handler.handleMessage(message)
                else:
                    raise Exception("Proxy doesn't have MessageHandler object!")
                obj_buffer = StringIO() # Reset buffer
            else:
                obj_buffer.write(line)
